﻿using NAudio.Wave;
using SharpDepend.Datatypes;
using SoundEngine3.AudioIO;
using SoundEngine3.AudioProcessing;
using SoundEngine3.DataTypes;
using SoundEngine3.Performance;
using System;
using System.Collections.Generic;

namespace Zquence_Studio_3_WindowsDesktop.SoundEngine.Classes
{
    public interface IPlaybackStream
    {
        int Read(float[] buffer, int offset, int sampleCount);
    }

    public abstract class WasapiDirectSoundShared : BaseAudioDriver, IPlaybackStream
    {
        public enum MillisecondsLatency
        {
            _10 = 10,
            _20 = 20,
            _30 = 30,
            _40 = 40,
            _50 = 50,
            _60 = 60,
            _70 = 70,
            _80 = 80,
            _90 = 90,
            _100 = 100,
            _150 = 150,
            _200 = 200,
            _250 = 250,
            _300 = 300,
            _400 = 400,
            _500 = 500,
        }

        private bool mFirstInit;
        private List<FastStructList<float>> mProcessedBuffers = new List<FastStructList<float>>();
        private Pool<FastStructList<float>> processBufferPool = new Pool<FastStructList<float>>();

        protected readonly AudioBufferProcessor AudioBufferProcessor;

        protected readonly RecordHelper RecordHelper = new RecordHelper();

        public WasapiDirectSoundShared(string name)
            : base(name)
        {
            AudioBufferProcessor = new AudioBufferProcessor(Name);
            AudioBufferProcessor.BufferNeeded += MAudioBufferProcessor_BufferNeeded;
        }

        protected abstract void FeatchInputSamples(Channel inputChannel);

        public bool IsInitialized { get { return mFirstInit; } }

        /// <summary>
        /// Return false if the initialization failed and will retry next time.
        /// <para>If not override this, it will return true always.</para>
        /// </summary>
        /// <returns>True if init succeded, false if failed and will recall OnInit until success.</returns>
        protected virtual bool OnInit()
        {
            return true;
        }

        protected virtual void OnDestroy()
        {
        }

        public void Destroy()
        {
            OnDestroy();
            mFirstInit = false;
        }

        private void MAudioBufferProcessor_BufferNeeded(BaseAudioBuffer output)
        {
            // Copy input audio.
            int inputsChannels = InputChannels;
            for (int i = inputsChannels - 1; i >= 0; i--)
            {
                FeatchInputSamples(AudioInput.Channels[i]);
            }

            // Process and get buffer when nessesary.
            RaiseOnBufferUpdate(output);
        }

        public void Config(int outputChannels, int bufferSize)
        {
            AudioBufferProcessor.Config(outputChannels, bufferSize);
        }

        public int Read(float[] buffer, int offset, int sampleCount)
        {
            if (sampleCount == 0)
            {
                return 0;
            }

            lock (this)
            {
                if (!mFirstInit)
                {
                    mFirstInit = OnInit();

                    if (!mFirstInit)
                    {
                        return sampleCount;
                    }
                }

                int count = 0;
                while (GetProcessedBufferLength(mProcessedBuffers) < sampleCount)
                {
                    ++count;

                    // Clear output audio.
                    foreach (var channel in AudioOutput.Channels)
                    {
                        Array.Clear(channel.Samples, 0, channel.Samples.Length);
                    }

                    // Get the buffers in calm and nice time.... Good swenglish :)
                    AudioBufferProcessor.GetBuffer(AudioOutput);

                    // Copy audio to the speakers.
                    FastStructList<float> processed = processBufferPool.GetObject();
                    processed.ResetLength(PreferredBufferSize * OutputChannels);

                    int outputChannels = OutputChannels;
                    for (int i = 0; i < outputChannels; i++)
                    {
                        double[] completeOutputArray = AudioOutput.Channels[i].Samples;

                        int slength = AudioOutput.Channels[i].Samples.Length;

                        int channelPosition = i;
                        int chanelOffset = outputChannels;
                        int index = -1;
                        for (int j = channelPosition; j < slength * outputChannels; j += chanelOffset)
                        {
                            double doubleValue = completeOutputArray[++index];
                            processed[j] = (float)doubleValue;
                        }
                    }
                    mProcessedBuffers.Add(processed);
                }

                if (mProcessedBuffers.Count == 0)
                {
                    System.Diagnostics.Debugger.Break();
                }

                // Write to total buffer.
                FastStructList<float> arrayTotal = processBufferPool.GetObject();
                arrayTotal.ResetLength(GetProcessedBufferLength(mProcessedBuffers));

                int goingLength = 0;
                for (int i = 0; i < mProcessedBuffers.Count; i++)
                {
                    int bLength = mProcessedBuffers[i].Count;
                    Array.Copy(mProcessedBuffers[i].ToArray(), 0, arrayTotal.ToArray(), goingLength, bLength);
                    goingLength += bLength;
                }

                // Write requested buffer.
                for (int i = 0; i < sampleCount; i++)
                {
                    buffer[i + offset] = arrayTotal[i];
                }
                //Array.Copy(arrayTotal.ToArray(), 0, buffer, offset, sampleCount); // TODO: Why it crash with MissMatch?
                processBufferPool.ReleaseObject(arrayTotal);

                // Remove used buffers.
                int length = sampleCount;
                int debug_times = 0;
                while (true)
                {
                    if (sampleCount >= mProcessedBuffers[0].Count)
                    {
                        var buff = mProcessedBuffers[0];
                        length -= buff.Count;
                        processBufferPool.ReleaseObject(buff);
                        mProcessedBuffers.RemoveAt(0);

                        if (mProcessedBuffers.Count == 0)
                        {
                            if (length != 0)
                            {
                                // Huh? It should be 0?
                            }
                            break;
                        }
                    }
                    else
                    {
                        FastStructList<float> arr = mProcessedBuffers[0];
                        int newLength = arr.Count - length;

                        Array.Copy(arr.ToArray(), length, arr.ToArray(), 0, newLength);
                        arr.SetLength(newLength);

                        break;
                    }

                    ++debug_times;
                }

                return sampleCount;
            }
        }

        static int GetProcessedBufferLength(List<FastStructList<float>> buffers)
        {
            int size = 0;
            for (int i = buffers.Count - 1; i >= 0; i--)
            {
                size += buffers[i].Count;
            }
            return size;
        }

        internal class WaveProvider32 : IWaveProvider
        {
            WaveBuffer waveBuffer = new WaveBuffer(0);

            WasapiDirectSoundShared mInstance;
            private WaveFormat waveFormat;

            public WaveProvider32(WasapiDirectSoundShared instance, int sampleRate, int channels)
                : this(sampleRate, channels)
            {
                mInstance = instance;
            }

            public WaveProvider32(int sampleRate, int channels)
            {
                SetWaveFormat(sampleRate, channels);
            }

            public void SetWaveFormat(int sampleRate, int channels)
            {
                this.waveFormat = WaveFormat.CreateIeeeFloatWaveFormat(sampleRate, channels);
            }

            public int Read(byte[] buffer, int offset, int count)
            {
#if DEBUG
                waveBuffer.BindTo(buffer);
                int samplesRequired = count / 4;
                int samplesRead = mInstance.Read(waveBuffer.FloatBuffer, offset / 4, samplesRequired);
                return samplesRead * 4;
#else
                try
                {
                    waveBuffer.BindTo(buffer);
                    int samplesRequired = count / 4;
                    int samplesRead = mInstance.Read(waveBuffer.FloatBuffer, offset / 4, samplesRequired);
                    return samplesRead * 4;
                }
                catch (Exception error)
                {
                    mInstance.Crash(error);
                    throw;
                }
#endif
            }

            public WaveFormat WaveFormat
            {
                get { return waveFormat; }
            }
        }
    }
}
